iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Claude AI

Claude × Playwright:從新進同事到 Agentic SDET 代理人系列 第 29 篇

Day 29|這支測試怎麼有時通過、有時失敗?

  • 分享至 

  • xImage
  •  

前言

今天早上新同事告訴我,昨天,它根據失敗記錄追查測試案例失敗原因。但它發現程式碼沒有變動,雖然這次測試失敗,再執行一次後測試案例就通過了。這時候可能會想,既然測試案例通過,那也許是環境或是其他原因,但我跟他說,即使如此,這個案例可能在下次執行時又失敗,所以我們必須多跑幾次,來測試看看這個案例是不是常常莫名的失敗。

開始之前

在相同條件下,偶爾通過、偶爾失敗的測試稱為 flaky test。今天我們使用 flaky-detect 來量測:先固定程式與環境,事先決定執行次數,最後記錄失敗的次數。

因為我們要看每次真正的結果,配套專案設了 retries: 0,不在失敗後自動重試。如果只盯著重試後的綠燈,就容易漏看前面失敗的那次。

前兩天看的都是購物車金額,今天要換一支測試。購物車那組的原因已經查清楚了:一組修好了,一組是產品缺陷,每次跑的結果都一樣,不會一下通過、一下失敗。要練習看不穩定的結果,得找一支本來就會晃的測試。這支跟昨天的修復無關,所以它今天失敗,不代表昨天修好的東西又壞了。

這次拿 tests/flaky/render-race.spec.ts 練習。它故意在搜尋後等一段固定時間,就直接讀取結果,等待多久由 FLAKY_WAIT_MS 決定。換句話說,它在賭「等這麼久,應該好了吧」。這是拿來示範問題的寫法,別帶回去當團隊範本。

量之前,先把條件講好。不管是交給助手,還是自己在終端機跑,都用同一組條件,兩邊的結果才比得起來:

條件 這次的值 為什麼要記
commit 執行當下的 git rev-parse HEAD 確認量的是哪一版測試
受測環境 SUT clean 配套設定的預設值;換到含缺陷版,就是另一件事了
Playwright 與瀏覽器版本 執行當下查到的版本 瀏覽器更新也可能改變時序
同時執行數 workers 4 本機預設是 4,CI 預設是 2;同時跑越多,時序越擠
等待時間 FLAKY_WAIT_MS 1350 測試裡的預設值,就是這次要賭的等待時間
重複次數 20 npm run test:flaky 已經設好 --repeat-each=20
自動重試 retries 0 配套設定就是 0,每次失敗都會留下來

接著,我們在 sdet-skills/ 開啟助手對話,把條件交代清楚:

請用 flaky-detect 量測 tests/flaky/render-race.spec.ts。
固定 SUT=clean、FLAKY_WAIT_MS=1350、workers=4、重複 20 次、不自動重試,也先不要修測試。
開跑前,把 commit、SUT、Playwright 與瀏覽器版本、workers、等待時間、重複次數和重試次數寫進本輪目錄的 params.txt。
保留每次結果與失敗訊息,回報失敗幾次、總共幾次,以及疑似原因。
本輪證據另存到 output/sessions/20260922_flaky-1350/,避免下輪覆蓋。

最後的目錄是這輪練習要存證據的位置,你可以換成自己的日期和名稱。這個例子指定跑 20 次;平常就先讀專案設定,把次數定下來。跑完才看結果,不要看到不喜歡的數字又偷偷加跑。

想自己在終端機試,先把條件記下來,再用同一組條件執行:

run_dir="output/sessions/20260922_flaky-1350"
mkdir -p "$run_dir"
{
  echo "commit: $(git rev-parse HEAD)"
  echo "sut: clean"
  echo "playwright: $(npx playwright --version)"
  echo "browser: $(npx playwright install --dry-run chromium | head -1)"
  echo "workers: 4"
  echo "flaky_wait_ms: 1350"
  echo "repeat_each: 20"
  echo "retries: 0"
} > "$run_dir/params.txt"
cat "$run_dir/params.txt"

SUT=clean FLAKY_WAIT_MS=1350 npm run test:flaky -- --workers=4

--workers=4 是寫明本機的預設值,這樣換到別台機器或 CI 也不會悄悄變成別的數字。跑完後,報告在 output/reports/playwright/,結果檔在 output/runs/playwright-results.json,失敗證據在 output/runs/playwright-artifacts/。

先把這些檔案搬進本輪目錄,和 params.txt 放在一起,再開始下一輪。因為預設輸出位置相同,下一輪會覆寫前一輪的資料:

cp output/runs/playwright-results.json "$run_dir/"
cp -R output/reports/playwright "$run_dir/report"
[ -d output/runs/playwright-artifacts ] && cp -R output/runs/playwright-artifacts "$run_dir/artifacts"
ls "$run_dir"

截圖、影片和操作紀錄只有在失敗時才會產生。20 次全部通過時,artifacts/ 裡只會有一個 .last-run.json,這是正常的。

跑完之後

跑完後,先整理你自己這一輪,再看我當年的紀錄。兩份資料不要混在一起:你手上的是新的一輪,後面那張等待時間表是舊資料。

第一步,把 20 次結果逐次列出來。同一支測試重複執行的結果,會依序排在結果檔裡,所以第幾筆就是第幾次:

jq -r '[.. | objects | select(has("specs")) | .specs[] | .tests[]] | to_entries[]
  | "\(.key + 1)\t\(.value.results[0].status)\t\(((.value.results[0].error.message // "")
  | gsub("\u001b\\[[0-9;]*m"; "") | split("\n") | .[0]) // "")"' \
  "$run_dir/playwright-results.json" > "$run_dir/runs.tsv"
cat "$run_dir/runs.tsv"

每一行是「第幾次、結果、失敗訊息第一行」。接著把失敗依訊息分組:

awk -F'\t' '$2 != "passed" {print $3}' "$run_dir/runs.tsv" | sort | uniq -c

只有一種訊息,就是一組。如果一種是逾時、另一種是別的錯誤,就要分開記,後面也要分開查。

有了逐次結果和分組,就可以寫交接紀錄。下面是空白範本,欄位沿用後面舊紀錄的格式,另外加上本輪條件、失敗分組、交給誰和證據位置:

::: details 展開量測交接範本:flaky-handoff.yaml

nodeid: "tests/flaky/render-race.spec.ts > 搜尋結果應該在送出後立刻可讀"
params:                  # 從 params.txt 抄過來
  commit:
  sut:
  playwright:
  browser:
  workers:
  flaky_wait_ms:
  repeat_each:
  retries:
runs:                    # 總次數 N
failures:                # 失敗次數 k
flake_rate:              # 寫成 k/N
outcome:                 # mixed、all-failed 或 not-reproduced
failure_groups:          # 依失敗訊息第一行分組
  - message:
    count:
    run_numbers: []      # runs.tsv 裡的第幾次
suspected_root_cause:
classification:
confidence:
next_step:
handoff_to:              # 例如 test-heal、第 28 天的失敗分析,或結束本輪
evidence:
  params:                # 本輪目錄/params.txt
  per_run:               # 本輪目錄/runs.tsv
  results_json:          # 本輪目錄/playwright-results.json
  report:                # 本輪目錄/report/index.html
  failure_artifacts:     # 本輪目錄/artifacts/,全部通過時寫「無失敗證據」
limits:

:::

範本也可以交給助手填。回到對話,貼上這段:

請讀取本輪目錄的 params.txt、runs.tsv 和 playwright-results.json,依第 29 天的範本建立 flaky-handoff.yaml。
本輪目錄:(貼上 run_dir)
次數和分組以 runs.tsv 為準,證據路徑要確認檔案存在。
limits 寫清楚這輪能說明什麼、不能說明什麼。
這一輪只整理,不修測試。

limits 最容易寫過頭。20 次全部通過,只能寫「本輪未重現」,不能寫成「已經穩定」;20 次全部失敗,也只能寫「本輪沒看到時好時壞」,不能說它永遠不會通過。

寫完後,你應該能用一句話回報這一輪:失敗 k 次、總共 N 次,錯誤分成幾類,疑似原因是什麼,要交給誰。接手的人要看原始輸出,照 evidence 裡的路徑就找得到。

自己這一輪整理好了,接下來看我當年的紀錄。下面這張表是 2026 年 8 月 2 日、workers=4 時量的,不是你這一輪的結果。

當時,我們試了幾個不同的等待時間,每輪都跑 20 次。1350 和 1400 毫秒各多跑一輪,所以這兩列有兩份結果;其他列的「—」代表沒有第二輪紀錄:

等待時間(毫秒) 第一輪失敗次數/20 第二輪失敗次數/20 這輪怎麼看
700 20/20(100%) — 全部失敗,先查持續失敗原因
1000 20/20(100%) — 全部失敗,先查持續失敗原因
1200 18/20(90%) — 有紅有綠,失敗比例很高
1300 18/20(90%) — 有紅有綠,失敗比例很高
1350(預設值) 7/20(35%) 12/20(60%) 兩輪差 25 個百分點
1400 2/20(10%) 5/20(25%) 兩輪差 15 個百分點

先看前面幾列。等 700 毫秒,這輪 20 次全部失敗,就先交昨天的流程查原因。等 1200 或 1300 毫秒,雖然各失敗 18 次,還是有兩次通過,也就已經觀察到時好時壞。

再往下看,同樣等 1350 毫秒,第一輪失敗 7 次,第二輪卻失敗 12 次。設定沒變,量到的比例還是會不同。

所以交報告時,我們最好說「20 次裡失敗 7 次」,把總次數一起寫出來。只說失敗率 35%,別人看不出你量了多少,也很難判斷這個數字能相信到哪裡。

次數記好了,助手還得把疑似原因和下一步寫給接手的人。下面用第一輪 1350 毫秒的數字示範格式,並非當時原始紀錄的逐字節錄:

nodeid: "tests/flaky/render-race.spec.ts > 搜尋結果應該在送出後立刻可讀"
runs: 20
failures: 7
flake_rate: 7/20
suspected_root_cause: wait-condition
classification: wait-timing
confidence: medium
next_step: "改成等待搜尋結果符合條件,再讀取並核對內容"

這裡先把等待條件列為疑似原因,交給後續修復。你可能會想,那就再多等一點?但等久了比較少失敗,仍沒回答「畫面到什麼狀態才可以讀值」,這件事還要繼續查。

順帶一提,tests/README.md 還記了一個插曲。另一批 tests/broken/ 的壞測試,原本綁死某段顯示文字,結果自己也時紅時綠。連示範「一定會壞」都有失手的時候。後來改成尋找不存在的 CSS 類別,才固定失敗。

背後怎麼做

回到今天的量測。次數跑滿後,我們就按這輪看到的結果,決定下一步:

結果 下一步
有失敗,也有通過 記下比例、疑似原因,交對應流程處理
全部失敗 本輪未觀察到時紅時綠,先走第 28 天的失敗分析
全部通過 記為本輪未重現(not-reproduced),結束這輪量測

這張表只處理眼前這一輪。20 次全過,只能說這次沒重現;20 次全失敗,也不能保證它以後永遠不會過。之後有新證據,可以再量,但前一輪的結果要留著。

如果確實有時好時壞,接手的人還需要知道從哪查。助手會先記下疑似原因,大致分成下面幾種:

疑似原因欄位 調查方向
wait-condition 元素出現了,但還不能操作或讀值
data-pollution 其他測試留下資料或狀態
parallel-race 同時搶用帳號或同一筆資料
external-dependency 受外部服務、時間、時區或亂數影響
render-timing 動畫、非同步更新或重繪的時機不同

這些名稱用來說明「懷疑哪裡出了問題」。另外還有 classification,要沿用昨天的分類,讓下一個流程知道怎麼接。所以上面同時寫了 wait-condition 和 wait-timing,分別是疑似原因與交接分類。

這也是為什麼每次失敗訊息都要留。如果一次是逾時,另一次是帳號被鎖,可能有兩個問題,需要分開查,不能用一個失敗比例就帶過。

查到需要改測試,就交 test-heal;改完再請 re-run-gate 照設定重跑驗收。若要暫時把這支測試移出會擋住發布的檢查,再由 flaky-manager 管理隔離和恢復。今天先把量測做好,這些後續動作還沒有實際執行。

今天學到什麼

現在再說這支測試不穩,我們就有數字可以拿出來:跑了幾次、失敗幾次,每次錯在哪裡。同一個 1350 毫秒設定,兩輪結果仍有差距,所以回報時要把樣本數和證據一起帶上。

走到這裡,找問題、修問題、照顧測試,各自都有做法了。明天是最後一天,我們把它們接成一輪值班,試試看這位新同事能接著做完多少。


上一篇
第 28 天|測試在 CI 上失敗,我連續猜錯兩次原因
系列文
Claude × Playwright:從新進同事到 Agentic SDET 代理人 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言